package tasks;

import exceptions.ChangeHostTypeException;
import exceptions.TaskException;
import graph.adapters.MockAdapter;
import graph.filters.ASTNodeTypeFilter;
import graph.filters.IFilter;
import graph.iterators.ASTNodeTreeClaimer;
import graph.searchers.Searcher;
import graph.visitors.AssignmentVisitor;

import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.eclipse.core.commands.Command;
import org.eclipse.core.commands.CommandManager;
import org.eclipse.core.commands.ParameterizedCommand;
import org.eclipse.core.commands.contexts.ContextManager;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.jdt.core.IBuffer;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.Assignment;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
import org.eclipse.jdt.core.dom.rewrite.ImportRewrite;
import org.eclipse.jdt.internal.corext.dom.Bindings;
import org.eclipse.jdt.internal.ui.text.correction.ASTResolving;
import org.eclipse.jface.bindings.BindingManager;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.IDocument;
import org.eclipse.text.edits.MalformedTreeException;
import org.eclipse.text.edits.MultiTextEdit;
import org.eclipse.text.edits.TextEdit;
import org.eclipse.text.edits.UndoEdit;


public class ChangeHostTypeTask implements ITask {
	
	private ITypeBinding _foundInterface= null;
	
	private VariableDeclarationFragment _variableDeclarationFragment = null;
	
	//private VariableDeclarationStatement _variableDeclarationStatement = null;
	
	private Type _declarationType = null;
	
	private boolean _isActive = true;
	
	private Error _lastError = null;

	private CompilationUnit _compilationUnit = null;

	private ICompilationUnit _iCompilationUnit = null;

	private IDocument _document = null;

	private UndoEdit _undo = null;
	
	private static final String _taskName = "Change Hosting Type";
	
	public ChangeHostTypeTask(Type declarationType,//VariableDeclarationStatement variableDeclarationStatement,
		VariableDeclarationFragment variableDeclarationFragment,
		ITypeBinding foundInterface) {
		_declarationType=declarationType;
		//_variableDeclarationStatement=variableDeclarationStatement;
		_variableDeclarationFragment=variableDeclarationFragment;
		_foundInterface=foundInterface;
		System.out.print("ChangeHostTypeTask>Created ChangeHostTypeTask on "+_variableDeclarationFragment.toString()+" to "+_foundInterface.getQualifiedName()+" with methods: ");
		for (IMethodBinding iMB: _foundInterface.getDeclaredMethods())
			System.out.print(iMB.getName()+"; ");
		System.out.println();
		_compilationUnit  = ASTResolving.findParentCompilationUnit(_variableDeclarationFragment);
		_iCompilationUnit  = (ICompilationUnit)_compilationUnit.getJavaElement();
		try {
			_document = new Document(_iCompilationUnit.getSource());
		} catch (JavaModelException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	@Override
	public boolean isActive() {
		return _isActive;
	}
	
	private ASTNode getParentNode(ASTNode child, int nodeType ) {
		Searcher<Integer, ASTNode, ASTNode> s = new Searcher<Integer, ASTNode, ASTNode>();
		Iterator<ASTNode> treeClaimer = new ASTNodeTreeClaimer(child);
		/**Filters ASTNodes by NodeType*/
		IFilter<Integer, ASTNode> nodeTypeFilter = new ASTNodeTypeFilter();
		/**Cast ASTNode to MethodDeclaration*/
		nodeTypeFilter.setEtalon(nodeType);
		List<ASTNode> searchResults = s.search(treeClaimer, nodeTypeFilter, new MockAdapter<ASTNode>());
		if (searchResults.size()==1)
			return searchResults.get(0);
		//else
		//	throw new Exception("Wrong number of found nodes");
		//TODO:Exceptions
		return null;
	}
	
	@Override
	public String toString() {
		return "in variable declaration fragment "+_variableDeclarationFragment.toString()+" from "+_declarationType.toString()+" to interface "+_foundInterface.getQualifiedName()+".";
	}

	@Override
	public void perform(Map<String,String> params) throws TaskException {
		/** isAssignmentCompatible*/
		String error = null;
		ASTNode parentNode = getParentNode(_variableDeclarationFragment, ASTNode.METHOD_DECLARATION);
		if (parentNode==null) 
			parentNode = getParentNode(_variableDeclarationFragment, ASTNode.TYPE_DECLARATION);
		if (parentNode==null) {
			error = "ChangeHostTypeTask>Variable "+_variableDeclarationFragment.getName().getFullyQualifiedName()+" doesn't have parent method";
			System.out.println(error);
			throw new ChangeHostTypeException(error);
		}
		
		AssignmentVisitor aV = new AssignmentVisitor();
		parentNode.accept(aV);
		for (Assignment a:aV.getAssignments())
			if (a.getRightHandSide().resolveTypeBinding().isEqualTo(_declarationType.resolveBinding())) {
				System.out.println("ChangeHostTypeTask>Class "+_declarationType.resolveBinding().getQualifiedName()
					+" participates in the assignment "+a.toString()+" at the right");
				if (!a.getLeftHandSide().resolveTypeBinding().isAssignmentCompatible(_foundInterface)) {//TODO:check
					error = "ChangeHostTypeTask>Left side of Assignment "+a.toString()+" is not assignment compatible with a new interface "+_foundInterface.getQualifiedName();
					System.out.println(error);
					_isActive = false;
					throw new ChangeHostTypeException(error);
				} else {
					System.out.println("ChangeHostTypeTask>Left side of Assignment "+a.toString()+" is assignment compatible with a new interface "+_foundInterface.getQualifiedName());
				}
			}
				
		ImportRewrite importRewrite = ImportRewrite.create(_compilationUnit, true);
		ASTRewrite rewriter = ASTRewrite.create(_compilationUnit.getAST());
		Type importedType = importRewrite.addImport(_foundInterface, rewriter.getAST());
		rewriter.replace(_declarationType, importedType, null);
		MultiTextEdit edit = new MultiTextEdit();
		try {
			TextEdit importsUpdate = importRewrite.rewriteImports(null);
			TextEdit hostTypesUpdate = rewriter.rewriteAST(_document, _iCompilationUnit.getJavaProject().getOptions(true));
			edit.addChild(importsUpdate);
			edit.addChild(hostTypesUpdate);//rewriter.rewriteAST());
			_undo = edit.apply(_document);
		} catch (Exception e) {
			error = "ChangeHostTypeTask>Unable to apply changes: "+e.getMessage();
			throw new ChangeHostTypeException(error);
		}
		// update of the compilation unit
		try {
			IBuffer buffer = _iCompilationUnit.getBuffer();
			buffer.setContents(_document.get());
			buffer.save(null, true);
			_iCompilationUnit.reconcile(ICompilationUnit.NO_AST,true,true,null,null);
		} catch (Exception e) {
			error = "ChangeHostTypeTask>Unable to update compilation unit: "+e.getMessage();
			throw new ChangeHostTypeException(error);
		}
		
		_isActive = false;
	}

	@Override
	public void undo() {
		try {
			if (_undo!=null)
				_undo.apply(_document);
		} catch (MalformedTreeException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (BadLocationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		// update of the compilation unit
		try {
			_iCompilationUnit.getBuffer().setContents(_document.get());
		} catch (JavaModelException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		_isActive = true;
		System.out.println("ImplementsTask>ImplementsTask perform rolled back");
	}

	@Override
	public String getDescription() {
		return toString();
	}

	@Override
	public Error getLastError() {
		return _lastError;
	}
	
	private ICompilationUnit getIcompilationUnit() {
		IJavaElement iCompilationUnitOfSourceClass =  Bindings.getBindingOfParentType(_variableDeclarationFragment).getJavaElement(); 
		while (iCompilationUnitOfSourceClass.getElementType() != IJavaElement.COMPILATION_UNIT)
			iCompilationUnitOfSourceClass = iCompilationUnitOfSourceClass.getParent();
		return (ICompilationUnit)iCompilationUnitOfSourceClass;
	}

	@Override
	public String getTargetModuleName() {
		return getIcompilationUnit().getElementName();
	}

	@Override
	public Map<String, String> getParameters() {
		return new HashMap<String, String>();
	}

	@Override
	public String getTaskName() {
		return _taskName;
	}

	@Override
	public IPath getPath() {
		return getIcompilationUnit().getPath();
	}

}
